package org.dreamwork.seagragon.filesystem;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
import org.dreamwork.misc.MimeTypeManager;
import org.dreamwork.seagragon.ImitationHttpSession;
import org.dreamwork.seagragon.ReadOnlyEnumeration;
import org.dreamwork.seagragon.SeaGragonClassLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.servlet.*;
import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 基于文件系统的 ServletContext 对象。
 *
 * <p><code>FSServletContext</code> 使用文件系统来模拟真正的 ServletContext,使<strong>部分</strong> java web应用能够
 * 脱离 Servlet Container 执行。<code>FSServletContext</code> 是 dreamwok seagragon 的核心类。</p>
 *
 * <p>任意一个对 ServletContext 的请求或操作，将被转换成对文件系统的操作，如{@link #getRealPath(String) getRealPath()}等。
 *
 * <pre>Created by IntelliJ IDEA.
 * User: seth
 * Date: 2010-4-24
 * Time: 18:13:33</pre>
 */
public class FSServletContext implements ServletContext {
    private String contextName;
    private File basedir;

    private Map<String, Object> attributes = new ConcurrentHashMap<String, Object> ();
    private Map<String, String> initParameters = new ConcurrentHashMap<String, String> ();
    private Map<String, Servlet> servlets = new ConcurrentHashMap<String, Servlet> ();
    private Map<String, String> servletName = new ConcurrentHashMap<String, String> ();
    private Map<String, String> servletMap = new ConcurrentHashMap<String, String> ();
    private Map<String, ServletConfig> configs = new ConcurrentHashMap<String, ServletConfig> ();
    private Map<String, RequestDispatcher> dispatchers = new ConcurrentHashMap<String, RequestDispatcher> ();
    private Map<Double, String> autoStartupServlets = new ConcurrentHashMap<Double, String> ();

    private DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
    private DocumentBuilder builder = dbf.newDocumentBuilder ();

    private SeaGragonClassLoader loader;
    private CallbackFilter filter = new HttpServletCallbackFilter ();

    private PrintWriter logger;

    /**
     * 构造函数
     *
     * <p>使用<code>contextName</code>, <code>basedir</code> 和 <code>out</code> 构建模拟的 ServletContext.
     * <p>FSServletContext 将会在当前系统用户的宿主目录下创建临时目录 <em>.seagragon</em>, 该目录作为 J2EE 标准中的临时
     * 目录使用，其在 ServletContext 中的索引值为 <code>javax.servlet.context.tempdir</code>。同时，FSServlet 还将缓存一个索引为 <code>org.apache.catalina.jsp_classpath</code> 的字符串字面量值，用以描述
     * FSServletContext 及其中使用的类的类加载目录。
     * <p>应用程序可以使用<code>ServletContext.getAttribute (String)</code>方法来访问这2个预置的对象。
     * @param contextName servlet context 名称。
     * @param basedir web 应用的物理目录。
     * @param out 模拟的 Http 响应的输出目标流。
     * @throws IOException IO异常
     * @throws SAXException XML SAX解析异常
     * @throws ParserConfigurationException 转化异常
     */
    public FSServletContext (String contextName, File basedir, OutputStream out) throws IOException, SAXException, ParserConfigurationException {
        logger = new PrintWriter (out, true);
        this.basedir = basedir;
        this.contextName = contextName;
        loader = new SeaGragonClassLoader (basedir);
        readWebConfig ();
        File file = new File (System.getProperty ("user.home"), ".seagragon/" + contextName);
        if (!file.exists () && !file.mkdirs ())
            throw new IOException ("Can't create directory: " + file.getCanonicalPath());
        setAttribute ("javax.servlet.context.tempdir", file);
        setAttribute ("org.apache.catalina.jsp_classpath", loader.getClassPath ());
    }

    /**
     * 构造函数
     *
     * <p>使用<code>contextName</code>, <code>basedir</code> 和 默认的 <code>System.out</code>来构建ServletContext
     *
     * @param contextName servlet context 名称。
     * @param basedir web 应用的物理目录。
     * @throws IOException IO异常
     * @throws SAXException XML SAX解析异常
     * @throws ParserConfigurationException 转化异常
     * @see #FSServletContext(String, java.io.File, java.io.OutputStream) 
     */
    public FSServletContext (String contextName, File basedir) throws IOException, SAXException, ParserConfigurationException {
        this (contextName, basedir, System.out);
    }

    public ServletContext getContext (String s) {
        return this;
    }

//    @Override
    public String getContextPath () {
        return '/' + contextName;
    }

    public int getMajorVersion () {
        return 0;
    }

    public int getMinorVersion () {
        return 1;
    }

    @Override
    public int getEffectiveMajorVersion () {
        return 0;
    }

    @Override
    public int getEffectiveMinorVersion () {
        return 0;
    }

    public String getMimeType (String s) {
        return MimeTypeManager.getType (s);
    }

    public Set getResourcePaths (String s) {
        return null;
    }

    public URL getResource (String s) throws MalformedURLException {
        File file = new File (basedir, s);
        return file.toURI().toURL ();
    }

    /**
     * 返回代表资源的InputStream
     *
     * <p>直接将资源名称转成相对于{@link #basedir basedir}的文件，并返回一个FileInputStream对象
     *
     * @param s 资源名称
     * @return 代表资源的 InputStream
     */
    public InputStream getResourceAsStream (String s) {
        File file = new File (basedir, s);
        try {
            return file.toURI().toURL ().openStream ();
        } catch (IOException e) {
            return null;
        }
    }

    public RequestDispatcher getRequestDispatcher (String url) {
        RequestDispatcher dispatcher = dispatchers.get (url);
        if (dispatcher != null) return dispatcher;

        FSRequestDispatcher fsrd = new FSRequestDispatcher (this, url);
        dispatchers.put (url, fsrd);
        return fsrd;
    }

    public RequestDispatcher getNamedDispatcher (String s) {
        return dispatchers.get (s);
    }

    @Deprecated
    public Servlet getServlet (String s) throws ServletException {
        return null;
    }

    /**
     * 根据url 来查找 Servlet
     *
     * <p>根据指定的<code>url</code>来查找Servlet实例。<br>
     * 首先，从缓存中查找指定的Servlet实例，若找到，返回该实例<br>
     * 若servlet未被缓存，从类路径中加载，缓存、并初始化(Servlet.init)，然后返回该实例
     *
     * @param url 指定的url
     * @return Servlet 实例
     * @throws ServletException 加载过程或初始化过程中的任意异常都将被转化成 ServletException 异常
     */
    public Servlet findServlet (String url) throws ServletException {
        int pos = url.indexOf ('?');
        if (pos > -1) url = url.substring (0, pos);
        Servlet servlet = servlets.get (url);
        if (servlet != null) return servlet;

        try {
            if (servletName.containsKey (url)) {
                initServlet (url);
                return servlets.get (url);
            }
            String name = getServletMapping (url);
            if (name != null) {
                initServlet (name);
                return servlets.get (name);
            }

            return null;
        } catch (Throwable th) {
            throw new ServletException (th);
        }
    }

    @Deprecated
    public Enumeration getServlets () {
        return null;
    }

    @Deprecated
    public Enumeration getServletNames () {
        return null;
    }

    public void log (String s) {
        logger.println (s);
    }

    @Deprecated
    public void log (Exception e, String s) {
        log (s, e);
    }

    public void log (String s, Throwable throwable) {
        logger.println (s);
        throwable.printStackTrace (logger);
    }

    public String getRealPath (String s) {
        File file = new File (basedir, s);
        return file.getAbsolutePath ();
    }

    public String getServerInfo () {
        return basedir.getAbsolutePath ();
    }

    public String getInitParameter (String s) {
        return initParameters.get (s);
    }

    public Enumeration getInitParameterNames () {
        return new ReadOnlyEnumeration (initParameters.keySet ());
    }

    @Override
    public boolean setInitParameter (String s, String s1) {
        return false;
    }

    public Object getAttribute (String s) {
        return attributes.get (s);
    }

    public Enumeration getAttributeNames () {
        return new ReadOnlyEnumeration (attributes.keySet ());
    }

    public void setAttribute (String s, Object o) {
        attributes.put (s, o);
    }

    public void removeAttribute (String s) {
        attributes.remove (s);
    }

    public String getServletContextName () {
        return contextName;
    }

    @Override
    public ServletRegistration.Dynamic addServlet (String s, String s1) {
        return null;
    }

    @Override
    public ServletRegistration.Dynamic addServlet (String s, Servlet servlet) {
        return null;
    }

    @Override
    public ServletRegistration.Dynamic addServlet (String s, Class<? extends Servlet> aClass) {
        return null;
    }

    @Override
    public <T extends Servlet> T createServlet (Class<T> aClass) throws ServletException {
        return null;
    }

    @Override
    public ServletRegistration getServletRegistration (String s) {
        return null;
    }

    @Override
    public Map<String, ? extends ServletRegistration> getServletRegistrations () {
        return null;
    }

    @Override
    public FilterRegistration.Dynamic addFilter (String s, String s1) {
        return null;
    }

    @Override
    public FilterRegistration.Dynamic addFilter (String s, Filter filter) {
        return null;
    }

    @Override
    public FilterRegistration.Dynamic addFilter (String s, Class<? extends Filter> aClass) {
        return null;
    }

    @Override
    public <T extends Filter> T createFilter (Class<T> aClass) throws ServletException {
        return null;
    }

    @Override
    public FilterRegistration getFilterRegistration (String s) {
        return null;
    }

    @Override
    public Map<String, ? extends FilterRegistration> getFilterRegistrations () {
        return null;
    }

    @Override
    public SessionCookieConfig getSessionCookieConfig () {
        return null;
    }

    @Override
    public void setSessionTrackingModes (Set<SessionTrackingMode> set) throws IllegalStateException, IllegalArgumentException {

    }

    @Override
    public Set<SessionTrackingMode> getDefaultSessionTrackingModes () {
        return null;
    }

    @Override
    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes () {
        return null;
    }

    @Override
    public void addListener (Class<? extends EventListener> aClass) {

    }

    @Override
    public void addListener (String s) {

    }

    @Override
    public <T extends EventListener> void addListener (T t) {

    }

    @Override
    public <T extends EventListener> T createListener (Class<T> aClass) throws ServletException {
        return null;
    }

    @Override
    public void declareRoles (String... strings) {

    }

    @Override
    public ClassLoader getClassLoader () {
        return null;
    }

    @Override
    public JspConfigDescriptor getJspConfigDescriptor () {
        return null;
    }

    private void readWebConfig () throws IOException, SAXException {
        File file = new File (basedir, "WEB-INF/web.xml");
        Document doc = builder.parse (file.toURI().toURL ().openStream ());
        NodeList list = doc.getDocumentElement ().getElementsByTagName ("context-param");
        for (int i = 0; i < list.getLength (); i++) {
            Node node = list.item (i);
            if (node.getNodeType () != Node.ELEMENT_NODE) continue;
            parseGenericNode (node, initParameters, "param-name", "param-value");
        }

        list = doc.getElementsByTagName ("servlet");
        for (int i = 0; i < list.getLength (); i++) {
            Node node = list.item (i);
            if (node.getNodeType () != Node.ELEMENT_NODE) continue;
            parseServletNode (node);
        }

        list = doc.getElementsByTagName ("servlet-mapping");
        for (int i = 0; i < list.getLength (); i ++) {
            Node node = list.item (i);
            if (node.getNodeType () != Node.ELEMENT_NODE) continue;
            parseGenericNode (node, servletMap, "url-pattern", "servlet-name");
        }

        list = doc.getElementsByTagName ("listener");
        for (int i = 0; i < list.getLength (); i ++) {
            Element e = (Element) list.item (i);
            NodeList listeners = e.getElementsByTagName ("listener-class");
            for (int j = 0; j < listeners.getLength (); j ++) {
                Node n = listeners.item (j);
                String className = n.getTextContent ();
                try {
                    Class c = loader.loadClass (className);
                    if (HttpSessionAttributeListener.class.isAssignableFrom (c)) {
                        HttpSessionAttributeListener hsal = (HttpSessionAttributeListener) c.newInstance ();
                        ImitationHttpSession.addListener (hsal);
                    } else if (ServletContextListener.class.isAssignableFrom (c)) {
                        ServletContextListener scl = (ServletContextListener) c.newInstance ();
                        scl.contextInitialized (new ServletContextEvent (this));
                    }
                } catch (Exception ex) {
                    ex.printStackTrace ();
                }
            }
        }

        double[] orders = new double [autoStartupServlets.size ()];
        int index = 0;
        for (Double I : autoStartupServlets.keySet ()) orders [index ++] = I;
        Arrays.sort (orders);
        for (double d : orders) {
            try {
                initServlet (autoStartupServlets.get (d));
            } catch (Throwable th) {
            }
        }
    }

    private void parseServletNode (Node node) {
        NodeList children = node.getChildNodes ();
        String name = null, value = null;
        for (int j = 0; j < children.getLength (); j++) {
            Node child = children.item (j);
            if (child.getNodeType () != Node.ELEMENT_NODE) continue;
            String nodeName = child.getNodeName ();
            if ("servlet-name".equals (nodeName)) name = child.getTextContent ();
            else if ("servlet-class".equals (nodeName)) value = child.getTextContent ();
            else if ("init-param".equals (nodeName)) {
                FSServletConfig config = new FSServletConfig (this, name);
                parseGenericNode (child, config.getInitParameters (), "param-name", "param-value");
                configs.put (name, config);
            } else if ("load-on-startup".equals (nodeName)) {
                String index = child.getTextContent ();
                if (!isEmpty (index)) {
                    try {
                        double d = Double.parseDouble (index);
                        while (autoStartupServlets.containsKey (d)) d += .1d;
                        autoStartupServlets.put (d, name);
                    } catch (Exception ex) {
                        // ignore
                    }
                }
            }
        }
        if (name != null && value != null) servletName.put (name, value);
    }

    private String parseGenericNode (Node node, Map<String, String> map, String nameField, String valueField) {
        NodeList children = node.getChildNodes ();
        String name = null, value = null;
        for (int j = 0; j < children.getLength (); j++) {
            Node child = children.item (j);
            if (child.getNodeType () != Node.ELEMENT_NODE) continue;
            String nodeName = child.getNodeName ();
            if (nameField.equals (nodeName)) name = child.getTextContent ();
            else if (valueField.equals (nodeName)) value = child.getTextContent ();
        }
        if (name != null && value != null) map.put (name, value);
        return name;
    }

    private void initServlet (String name) throws Throwable {
        String className = servletName.get (name);
        Class c = loader.loadClass (className);
        Enhancer enhancer = new Enhancer ();
        Callback[] callbacks = {new HttpServletCallback (this, configs.get (name)), NoOp.INSTANCE};
        enhancer.setSuperclass (c);
        enhancer.setCallbacks (callbacks);
        //添加 方法过滤器  返回1为不运行 2 为运行
        enhancer.setCallbackFilter (filter);
        HttpServlet servlet = (HttpServlet) enhancer.create ();
        servlets.put (name, servlet);
        servlet.init (configs.get (name));
    }

    private String getServletMapping (String path) {
        for (String s : servletMap.keySet ()) {
            String p = s.replace (".", "\\.");
            p = p.replace ("*", ".*");
            Pattern pt = Pattern.compile (p);
            Matcher m = pt.matcher (path);
            if (m.matches ()) return servletMap.get (s);
        }
        return null;
    }

    private boolean isEmpty (String str) {
        return str == null || str.trim ().length () == 0;
    }
}